﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Reflection;
using System.Data.SqlClient;

namespace Gedea.Core.Manager.DataAccess
{
    /*
     * Para utilizar esta clase es necesario que siempre se respete que su equivalente en el SP
     * tenga el mismo nombre que las propiedades, para que se obtenga un valor correcto.
     */
    public abstract class DataCommon
    {
        public T MapToEntity<T>(IDataReader dr, bool hasPrefix)
            where T : new()
        {
            T entity = new T();

            BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Static;

            foreach (PropertyInfo prop in entity.GetType().GetProperties(flags)
                .Where(p => ListDataReaderFields(dr).Contains(
                    GetOrdinalKey(hasPrefix, entity.GetType().Name, p.Name))))
            { //TO DO: Lista de string e ir removiendo cada uno que bindea. Tirar error si queda alguno colgado.
                int ordinal = GetNormalizedOrdinal(dr, GetOrdinalKey(hasPrefix, entity.GetType().Name, prop.Name));
                object value = GetNormalizedValue(dr, ordinal, prop.CanWrite);

                prop.SetValue(entity, value, null);
            }
            return entity;
        }

        protected void Exec(string query)
        {
            using (SqlConnection conn = new SqlConnection(DataConnection.GetConnection()))
            {
                using (SqlCommand cmd = new SqlCommand())
                {
                    conn.Open();
                    cmd.Connection = conn;
                    cmd.CommandText = query;
                    cmd.ExecuteNonQuery();
                    conn.Close();
                }
            }
        }

        protected T GetEntity<T>(string query) where T : new()
        {
            T entity = new T();

            using (SqlConnection conn = new SqlConnection(DataConnection.GetConnection()))
            {
                SqlCommand command = new SqlCommand(query, conn);
                conn.Open();

                SqlDataReader reader = command.ExecuteReader();

                if (reader.Read())
                    entity = MapToEntity<T>(reader, false);

                reader.Close();
                conn.Close();
            }
            return entity;
        }

        public List<T> ListEntity<T>(string query)
            where T : new()
        {
            List<T> entities = new List<T>();

            using (SqlConnection conn = new SqlConnection(DataConnection.GetConnection()))
            {
                SqlCommand command = new SqlCommand(query, conn);
                conn.Open();

                SqlDataReader reader = command.ExecuteReader();

                while (reader.Read())
                    entities.Add(MapToEntity<T>(reader, false));

                reader.Close();
                conn.Close();
            }
            return entities;
        }

        protected PropertyInfo GetPropertyEntityId<T>(PropertyInfo[] properties) where T : new()
        {
            return properties.ToList()
                .Where(x => x.Name == new T().GetType().Name + "Id")
                .FirstOrDefault();
        }

        protected string GetTableName<T>() where T : new()
        {
            //TO DO: Esto así o xml con equivalencias (y valor por defecto)
            // o Inflector
            return new T().GetType().Name + "s";
        }

        private static List<string> ListDataReaderFields(IDataReader dr)
        {
            List<string> drFields = new List<string>();
            for (int i = 0; i < dr.FieldCount; i++)
            {
                drFields.Add(dr.GetName(i));
            }
            return drFields;
        }

        private string GetOrdinalKey(bool hasPrefix, string prefix, string propName)
        {
            return hasPrefix ? prefix + "." + propName : propName;
        }

        private object GetNormalizedValue(IDataReader dr, int ordinal, bool propCanWrite)
        {
            return ordinal >= 0 && !dr.IsDBNull(ordinal) && propCanWrite ? dr.GetValue(ordinal) : null;
        }

        private int GetNormalizedOrdinal(IDataReader dr, string propertyName)
        {
            int ordinal;
            try
            {
                ordinal = dr.GetOrdinal(propertyName);
            }
            catch (ArgumentException)
            {
                ordinal = -1;
            }
            return ordinal;
        }
    }
}
